import _ from "lodash";
import Mock from "mockjs";
import axios, {AxiosInstance, AxiosResponse} from "axios";
import handleRequestFulfilled from "./handle-request-fulfilled.ts";
import handleResponseFulfilled from "./handle-response-fulfilled.ts";
import handleResponseRejected from "./handle-response-rejected.ts";
import {
    defaultCustomRequestConfig,
    defaultMockConfig,
    InternalRequestConfig,
    MockConfig,
    RequestConfig,
} from "./app-request-type.ts";
import AppRequestUtil from "./app-request-util.ts";

export default class AppRequest {
  axiosInstance: AxiosInstance;

  requestConfig: InternalRequestConfig;

  mockConfig: Required<MockConfig>;

  constructor(requestConfig: RequestConfig<any>, mockConfig?: MockConfig) {
    this.requestConfig = _.defaultsDeep(
        requestConfig,
        defaultCustomRequestConfig
    );
    this.mockConfig = mockConfig
        ? _.defaultsDeep(mockConfig, defaultMockConfig)
        : defaultMockConfig;

    // bind用于将调用对象中的this绑定为AppRequest内的this
    const axiosInstance = axios.create(this.requestConfig);
    axiosInstance.interceptors.request.use(handleRequestFulfilled.bind(this));
    axiosInstance.interceptors.response.use(
        handleResponseFulfilled.bind(this),
        handleResponseRejected.bind(this)
    );
    this.axiosInstance = axiosInstance;
  }

  // ####################### API #####################
  request<T = any, R = AxiosResponse<T>, D = any>(
      config: RequestConfig<D>
  ): Promise<R> {
    if (AppRequestUtil.isTemplateMock(config.url, this.mockConfig)) {
      const mockData = Mock.mock(
          AppRequestUtil.mockTemplate(config.url, this.mockConfig)
      );
      return handleResponseFulfilled.bind(this)(
          AppRequestUtil.constructMockTemplateAxiosResponse(mockData, config)
      );
    }

    return this.axiosInstance.request(config);
  }

  get<T = any, R = AxiosResponse<T>, D = any>(
      url: string,
      config?: RequestConfig<D>
  ): Promise<R> {
    if (AppRequestUtil.isTemplateMock(url, this.mockConfig)) {
      const mockData = Mock.mock(
          AppRequestUtil.mockTemplate(url, this.mockConfig)
      );
      return handleResponseFulfilled.bind(this)(
          AppRequestUtil.constructMockTemplateAxiosResponse(mockData, config)
      );
    }

    return this.axiosInstance.get(url, config);
  }

  delete<T = any, R = AxiosResponse<T>, D = any>(
      url: string,
      config?: RequestConfig<D>
  ): Promise<R> {
    if (AppRequestUtil.isTemplateMock(url, this.mockConfig)) {
      const mockData = Mock.mock(
          AppRequestUtil.mockTemplate(url, this.mockConfig)
      );
      return handleResponseFulfilled.bind(this)(
          AppRequestUtil.constructMockTemplateAxiosResponse(mockData, config)
      );
    }

    return this.axiosInstance.delete(url, config);
  }

  head<T = any, R = AxiosResponse<T>, D = any>(
      url: string,
      config?: RequestConfig<D>
  ): Promise<R> {
    if (AppRequestUtil.isTemplateMock(url, this.mockConfig)) {
      const mockData = Mock.mock(AppRequestUtil.mockTemplate(url, this.mockConfig));
      return handleResponseFulfilled.bind(this)(
          AppRequestUtil.constructMockTemplateAxiosResponse(mockData, config)
      );
    }

    return this.axiosInstance.head(url, config);
  }

  options<T = any, R = AxiosResponse<T>, D = any>(
      url: string,
      config?: RequestConfig<D>
  ): Promise<R> {
    if (AppRequestUtil.isTemplateMock(url, this.mockConfig)) {
      const mockData = Mock.mock(AppRequestUtil.mockTemplate(url, this.mockConfig));
      return handleResponseFulfilled.bind(this)(
          AppRequestUtil.constructMockTemplateAxiosResponse(mockData, config)
      );
    }

    return this.axiosInstance.options(url, config);
  }

  post<T = any, R = AxiosResponse<T>, D = any>(
      url: string,
      data?: D,
      config?: RequestConfig<D>
  ): Promise<R> {
    if (AppRequestUtil.isTemplateMock(url, this.mockConfig)) {
      const mockData = Mock.mock(
          AppRequestUtil.mockTemplate(url, this.mockConfig)
      );
      return handleResponseFulfilled.bind(this)(
          AppRequestUtil.constructMockTemplateAxiosResponse(mockData, config)
      );
    }

    return this.axiosInstance.post(url, data, config);
  }

  put<T = any, R = AxiosResponse<T>, D = any>(
      url: string,
      data?: D,
      config?: RequestConfig<D>
  ): Promise<R> {
    if (AppRequestUtil.isTemplateMock(url, this.mockConfig)) {
      const mockData = Mock.mock(
          AppRequestUtil.mockTemplate(url, this.mockConfig)
      );
      return handleResponseFulfilled.bind(this)(
          AppRequestUtil.constructMockTemplateAxiosResponse(mockData, config)
      );
    }

    return this.axiosInstance.put(url, data, config);
  }

  patch<T = any, R = AxiosResponse<T>, D = any>(
      url: string,
      data?: D,
      config?: RequestConfig<D>
  ): Promise<R> {
    if (AppRequestUtil.isTemplateMock(url, this.mockConfig)) {
      const mockData = Mock.mock(
          AppRequestUtil.mockTemplate(url, this.mockConfig)
      );
      return handleResponseFulfilled.bind(this)(
          AppRequestUtil.constructMockTemplateAxiosResponse(mockData, config)
      );
    }

    return this.axiosInstance.patch(url, data, config);
  }

  postForm<T = any, R = AxiosResponse<T>, D = any>(
      url: string,
      data?: D,
      config?: RequestConfig<D>
  ): Promise<R> {
    if (AppRequestUtil.isTemplateMock(url, this.mockConfig)) {
      const mockData = Mock.mock(
          AppRequestUtil.mockTemplate(url, this.mockConfig)
      );
      return handleResponseFulfilled.bind(this)(
          AppRequestUtil.constructMockTemplateAxiosResponse(mockData, config)
      );
    }

    return this.axiosInstance.postForm(url, data, config);
  }

  putForm<T = any, R = AxiosResponse<T>, D = any>(
      url: string,
      data?: D,
      config?: RequestConfig<D>
  ): Promise<R> {
    if (AppRequestUtil.isTemplateMock(url, this.mockConfig)) {
      const mockData = Mock.mock(
          AppRequestUtil.mockTemplate(url, this.mockConfig)
      );
      return handleResponseFulfilled.bind(this)(
          AppRequestUtil.constructMockTemplateAxiosResponse(mockData, config)
      );
    }

    return this.axiosInstance.putForm(url, data, config);
  }

  patchForm<T = any, R = AxiosResponse<T>, D = any>(
      url: string,
      data?: D,
      config?: RequestConfig<D>
  ): Promise<R> {
    if (AppRequestUtil.isTemplateMock(url, this.mockConfig)) {
      const mockData = Mock.mock(
          AppRequestUtil.mockTemplate(url, this.mockConfig)
      );
      return handleResponseFulfilled.bind(this)(
          AppRequestUtil.constructMockTemplateAxiosResponse(mockData, config)
      );
    }

    return this.axiosInstance.patchForm(url, data, config);
  }
}
